//
//  CircleLineLoadingView.swift
//  SwiftLearning
//
//  Created by 黄龙 on 2023/12/18.
//

import UIKit

//MARK: - 圆圈线条加载动画(弧形线条会自动缩短，然后变长，如此反复同时转圈)
class CircleLineLoadingView: UIView {

    var radius:CGFloat?
    var index:Int = 0
    var _duration:CFTimeInterval = 2.0
    var enable:Bool = true //为false时动画停止不再循环
    var _loadingLayer:CAShapeLayer?
    var _lineColor:UIColor = .label
    var _lineWidth = 3.0
    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */
    init(frame:CGRect,lineWidth:CGFloat=3.0,lineColor:UIColor = .label){
        super.init(frame: frame)
        _lineWidth = lineWidth
        _lineColor = lineColor
        self.initUI()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.initUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if let loadingLayer = _loadingLayer{
            let path = cycleBezierPathIndex(index: index)
            loadingLayer.path = path.cgPath
        }
    }
    
    func initUI(){
        radius   = self.bounds.width/2;
        _loadingLayer = CAShapeLayer()
        if let loadingLayer = _loadingLayer{
            loadingLayer.lineWidth = _lineWidth
            loadingLayer.fillColor = UIColor.clear.cgColor
            loadingLayer.strokeColor = _lineColor.cgColor
            loadingLayer.lineCap = .round
            self.layer.addSublayer(loadingLayer)
        }
    }
    
    func updateStrokeColor(_ color:UIColor){
        if let loadingLayer = _loadingLayer{
            loadingLayer.strokeColor = color.cgColor
        }
    }
    
    func updateLineWidth(_ lineWidth:CGFloat){
        if let loadingLayer = _loadingLayer{
            loadingLayer.lineWidth = lineWidth
        }
    }
    
    func updateRadius(_ newRadius:CGFloat){
        radius = newRadius
    }
    
    func updateDuration(_ duration:CFTimeInterval){
        _duration = duration
    }
 
    func cycleBezierPathIndex(index:Int)->UIBezierPath{
        if radius == nil{
            radius = self.bounds.width/2
        }
        let path = UIBezierPath(arcCenter: CGPoint(x: self.bounds.width/2, y: self.bounds.height/2), radius: radius!, startAngle: Double(index) * (Double.pi * 2)/3, endAngle: Double(index) * (Double.pi * 2)/3 + 2 * Double.pi * 4 / 3, clockwise: true) //(* 4 / 3)让线条衔接得更紧密一些，一整圈其实+2pi即可
        return path
    }
    
    func startAnimate(){
        if let loadingLayer = _loadingLayer{
            if let keys = loadingLayer.animationKeys(),keys.count > 0{
                return
            }
        }
        enable = true
        loadingAnimation()
    }
    
 
    func stopAnimation() {
        enable = false
        if let loadingLayer = _loadingLayer{
            loadingLayer.removeAllAnimations()
        }
    }
    
    func loadingAnimation(){
// "strokeStart"和"strokeEnd"，固定keypath不能拼错
        let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart") //The relative location at which to begin stroking the path. Animatable.
        strokeStartAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        strokeStartAnimation.fromValue = 0
        strokeStartAnimation.toValue = 1
/*
>strokeStart动画解析：
fromValue和toValue设置为[0,1]
此时，终点位置默认为1
strokeStart.fromValue = 0 时有一条完整的路径，strokeStart.toValue = 1 时路径消失。
所以，其效果就是从路径起点到终点,线条慢慢消失的动画：
比如本动画首个strokeStart动画，就是cycleBezierPathIndex(0)从0度到8pi/3的弧线，从0度开始向终点8pi/3(顺时针)慢慢消失1圈多的动画过程
---------
而如果如反过来，fromValue和toValue设置为[1,0]
此时，终点位置仍是默认为1
strokeStart = 1 时无路径，strokeStart = 0 时画出一条完整的路径。
其效果就是从路径终点反向到起点慢慢出现线条的动画：
如果用于本动画的首个strokeStart动画，就是cycleBezierPathIndex(0)从8pi/3的弧线到0度,(逆时针)慢慢画出1圈多线条的动画过程
*/
        
        let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd") //The relative location at which to stop stroking the path. Animatable.
        strokeEndAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        strokeEndAnimation.duration = _duration * 0.5
        strokeEndAnimation.fromValue = 0
        strokeEndAnimation.toValue = 1
/*
>strokeEnd动画解析：
fromValue和toValue设置为[0,1]
此时，起点位置默认为0
strokeEnd=0 时无路径，strokeEnd=1 时有一条完整路径。
其效果就是从路径起点到终点，线条慢慢出现的动画
比如本动画首个strokeEnd动画，就是cycleBezierPathIndex(0)从0度到8pi/3的弧线，从0度开始向终点8pi/3(顺时针)慢慢出现1圈多线条的动画过程
----------
反过来，fromValue和toValue设置为[1,0]
此时，起点位置仍旧默认为0
strokeEnd=1 时有一条完整路径，strokeEnd=0 时无路径(路径消失)
效果就是路径终点到起点,线条慢慢消失的动画
如用于本动画首个strokeEnd动画，就是cycleBezierPathIndex(0)从终点8pi/3到起点0度到的弧线，(逆时针)慢慢消失的动画过程
*/
        
//其实很简单，只需记住strokeStart默认终点位置为1，value=0表示有路径线条，value=1表示线条路径消失
//而strokeEnd默认起点位置为0，value=0表示无路径(线条路径消失)，value=1表示有路径线条
//正常设置的情史下，strokeStart是线条从有到无，慢慢消失的过程；而strokeEnd则是线条从无到有，慢慢出现的过程

        let strokeAniamtionGroup = CAAnimationGroup()
        strokeAniamtionGroup.duration = _duration
        strokeAniamtionGroup.fillMode = .forwards
        strokeAniamtionGroup.isRemovedOnCompletion = false
        strokeAniamtionGroup.delegate = self
        strokeAniamtionGroup.animations = [strokeStartAnimation,strokeEndAnimation]
//而本View采用了将strokeStart和strokeEnd动，形成一个组动画来显示，因为strokeEndAnimation时间更短，所以呈现的效果是strokeEnd不断绘制线条，而strokeStart紧随其后不断擦除线条的过程
        if let loadingLayer = _loadingLayer{
            loadingLayer.add(strokeAniamtionGroup, forKey: "strokeAnimationGroup")
        }
    }

}

extension CircleLineLoadingView:CAAnimationDelegate{
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if (!enable) {
            return;
        }
        
        if let loadingLayer = _loadingLayer{
            index += 1
            loadingLayer.path = cycleBezierPathIndex(index: index % 3).cgPath
            loadingAnimation()
        }
    }
}
